package com.aggregate.pay.pingan.support;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aggregate.pay.pingan.bean.CommonData;
import com.aggregate.pay.pingan.bean.CommonResult;
import com.aggregate.pay.pingan.bean.Result;
import com.aggregate.pay.pingan.support.utils.AES;
import com.aggregate.pay.pingan.support.utils.JSON;
import com.aggregate.pay.pingan.support.utils.RSA;
import com.aggregate.pay.pingan.support.utils.SHA1;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * 接口API操作类抽象类
 * @author Moyq5
 * @date 2017年6月15日
 * @param <D>
 * @param <R>
 */
public abstract class AbstractClient<D, R> implements Client<D, R> {

	private static Logger log = LoggerFactory.getLogger(AbstractClient.class);
	private static final String CHARSET = "UTF-8";
	private Context context;
	private Class<R> detailClass;
	private String pathType;
	protected AbstractClient(String pathType, Class<R> detailClass) {
		this.pathType = pathType;
		this.detailClass = detailClass;
	}
	
	@Override
	public Result<R> post(D data) {
		Result<R> result = new Result<R>();
		try {
			Config cfg = context.getConfig();
			HttpClient client = context.getHttpClient();
			String jsonString = null;
			if (data instanceof Dynamic) {
				jsonString = JSON.toString(((Dynamic)data).toFinal());
			} else {
				jsonString = JSON.toString(data);
			}
			log.debug("请求参数data明文：{}", jsonString);
			String dataEncrypt = AES.encrypt(jsonString, cfg.getOpenKey());
			log.debug("请求参数data密文：{}", dataEncrypt);
			CommonData reqData = buildCommonData(dataEncrypt);
			String reqString = getReqString(reqData);
			String reqBody = getReqBody(reqData);
			
			String serverPath = null;
			if (pathType.startsWith("http")) {
				serverPath = pathType;
			} else {
				serverPath = cfg.getServerPath() + pathType;
			}
			
			String resBody = client.post(serverPath + "?" + reqString, reqBody);
			CommonResult resResult = JSON.toObject(resBody, CommonResult.class);
			
			dataEncrypt = resResult.getData();
			R detail = null;
			if (null != dataEncrypt) {
				if (!checkSign(resResult)) {
					throw new Exception("签名失败");
				}
				log.debug("响应参数data密文：{}", dataEncrypt);
				jsonString = AES.decrypt(dataEncrypt, cfg.getOpenKey());
				log.debug("响应参数data明文：{}", jsonString);
				detail = JSON.toObject(jsonString, detailClass);
			}
			
			result.setCommon(resResult);
			result.setDetail(detail);
		} catch (Exception e) {
			log.error("接口调用失败", e);
			CommonResult comResult = new CommonResult();
			comResult.setCode(9999);
			comResult.setMsg(e.getMessage());
			result.setCommon(comResult);
		}
		return result;
	}
	
	/**
	 * 获取post请求参数
	 */
	private String getReqBody(CommonData data) {
		return String.format("data=%s&sign=%s", data.getData(), data.getSign());
	}

	/**
	 * 获取get请求参数
	 */
	private String getReqString(CommonData data) {
		List<String> ignoreFields = new ArrayList<String>();
		ignoreFields.add("sign");
		ignoreFields.add("data");
		return toQueryString(data, ignoreFields);
	}

	/**
	 * 签名验证
	 */
	private boolean checkSign(CommonResult result) throws UnsupportedEncodingException, Exception {
		Config cfg = context.getConfig();
		
		List<String> ignoreFields = new ArrayList<String>();
		ignoreFields.add("sign");
		
		String signString = toQueryString(result, ignoreFields);
		log.debug("签名验证内容：{}", signString);
		Boolean isValid = RSA.verify(SHA1.encrypt(signString).toLowerCase().getBytes(CHARSET), cfg.getPublicKey(), result.getSign());
		log.debug("签名验证结果：{}", isValid);
		if (!isValid) {
			log.error("签名验证失败，目标值：{}", result.getSign());
		}
		return isValid;
	}

	/**
	 * 创建最终请求参数对象
	 */
	private CommonData buildCommonData(String data) throws UnsupportedEncodingException, Exception {
		Config cfg = context.getConfig();
		
		CommonData com = new CommonData();
		com.setData(data);
		com.setLang("zh-cn");
		com.setOpenId(cfg.getOpenId());
		com.setRandStr(mkRandomStr(32));
		//com.setSign(sign);
		com.setTimestamp((int)(new Date().getTime()/1000));
		
		List<String> ignoreFields = new ArrayList<String>();
		ignoreFields.add("sign");
		
		String signString = toQueryString(com, ignoreFields);
		log.debug("签名内容：{}", signString);
		String sign = RSA.sign(SHA1.encrypt(signString).toLowerCase().getBytes(CHARSET), cfg.getPrivateKey()).toLowerCase();
		log.debug("签名值：{}", sign);
		com.setSign(sign);
		
		return com;
	}

	protected Context getContext() {
		return context;
	}

	protected void setContext(Context context) {
		this.context = context;
	}
	
	private static String toQueryString(Object obj, List<String> ignoreFields) {
		log.debug("将对象组装成get参数格式字符串");
		log.debug("源对象：{}", obj.getClass().getName());
		Map<String, String> names = getSortedFields(obj.getClass());
		StringBuffer sb = new StringBuffer();
		String value;
		Method method;
		String fieldName;
		for(Map.Entry<String, String> entry : names.entrySet()) {
			try {
				fieldName = entry.getValue();
				if (null != ignoreFields && ignoreFields.contains(fieldName)) {
					continue;
				}
				method = obj.getClass().getMethod("get" + 
						fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
				value = String.valueOf(method.invoke(obj)).trim();
				if (value.contentEquals("null")) {
					value = "";
				}
				sb.append(entry.getKey() + "=" + value + "&");
			} catch(Exception e) {
				log.warn("数据转换失败：{}", e.getMessage());
			}
		}
		if (names.size() > 0) {
			sb.delete(sb.length()-1, sb.length());// 去掉最后的“&”
		}
		log.debug("组装结果：{}", sb.toString());
		return sb.toString();
	}
	
	private static Map<String, String> getSortedFields(Class<?> clazz) {
		return getSortedFields(null, clazz);
	}
	
	private static Map<String, String> getSortedFields(TreeMap<String, String> names, Class<?> clazz) {
		if (null == names) {
			names = new TreeMap<String, String>();
		}
		if (clazz == Object.class) {
			return names;
		}
		Field[] fields = clazz.getDeclaredFields();
		JsonProperty prop;
		for (Field field : fields) {
			prop = field.getAnnotation(JsonProperty.class);
			String paramName;
			if (null != prop) {
				paramName = prop.value();
			} else {
				paramName = field.getName();
			}
			names.put(paramName, field.getName());
		}
		return getSortedFields(names, clazz.getSuperclass());
	}
	
	private String mkRandomStr(int length) {
		final char[] chars = "0123456789ABCDEF".toCharArray();
		StringBuffer sb = new StringBuffer();
	    for(int i = 0; i<length; i++){
	    	sb.append(chars[new Long(Math.round(Math.random() * (chars.length-1))).intValue()]);
	    }
		return sb.toString();
	}
}
